; rdrel.asm
; Relative file routines for FE3 RAM Disk
;

	.include "rdglobal.inc"
	.include "rdcmd.inc"
	.include "rdmain.inc"
	.include "rddir.inc"
	.include "rdio.inc"
	.include "rdmem.inc"
	.include "fe3def.inc"
	_IN_RDREL = 1
	.include "rdrel.inc"

	REL_STATE_CLOSE = 0
	REL_STATE_RECNEW = 1
	REL_STATE_INREC = 2
	REL_STATE_READVOID = 3
	REL_STATE_WRITENEW = 4
	REL_STATE_WRITEVOID = 5
	REL_STATE_WRITEREC = 6
	REL_STATE_READREC = 7
	
	REL_EVENT_OPEN = 0
	REL_EVENT_CLOSE = 1
	REL_EVENT_CKOUT = 2
	REL_EVENT_CHKIN = 3
	REL_EVENT_CLRCH = 4
	REL_EVENT_POS = 5
	REL_EVENT_BASIN = 6
	REL_EVENT_BSOUT = 7

.macro SET_STATE state
			pha
			txa
			pha
			ldx #state
			lda sttbl_lo, x
			sta stateptr
			lda sttbl_hi, x
			sta stateptr+1 
			pla
			tax
			pla
.endmacro

.macro HANDLE_EVENT event
			pha
			lda #event
			asl
			adc stateptr
			sta jmpgate+1
			lda stateptr+1
			adc #0
			sta jmpgate+2
			pla
			jmp jmpgate
.endmacro

.macro SET_RESULT status
			pha
			lda #status
			jsr cmd_setst
			pla
.endmacro

.macro SET_EOF
			pha
			lda #$40				; ST := EOF
			jsr $FE6A
			pla
.endmacro

.macro PUSHXY
			sta temp
			txa
			pha
			tya
			pha
			lda temp
.endmacro

.macro POPXY
			sta temp
			pla
			tay
			pla
			tax
			lda temp
.endmacro

	.segment "BSS"
	
temp:		.res 1

rec_chn:	.res 1					; file channel of file in relative access mode
rec_info:	.res 1					; index of file info
rec_size:	.res 1					; record size
rec_no:		.res 2					; record # (0 .. first)
rec_pos:	.res 1					; position in record (0 .. first byte)
rec_dir:	.res 2					; dir entry for open file
rec_block:	.res 2					; block
rec_offset:	.res 1					; offset in block
rec_bank:	.res 1					; bank in Super RAM of current r/w position
rec_addr:	.res 1					; high byte of address in Super RAM

stateptr:	.res 2

	.segment "CODE"
	
jmpgate:	jmp ($F000)

;----------------------------------------------------------
; REL File interface implementation
	
; Initialises relative file handling
;
rel_init:	SET_STATE REL_STATE_CLOSE
			rts

; Opens a relative file
; INPUT:
;	A ....... channel no.
;	Y ....... record size
;	X ....... file info index
;	(ptr) ... dir entry
;
rel_open:	HANDLE_EVENT REL_EVENT_OPEN

; Closes REL file
;
rel_close:	HANDLE_EVENT REL_EVENT_CLOSE

; Prepares relative reading
;
rel_chkin:	pha
			lda #<rel_basin
			sta rdhook
			lda #>rel_basin
			sta rdhook+1
			pla
			HANDLE_EVENT REL_EVENT_CHKIN

; Prepares relative writing
;
rel_ckout:	pha
			lda #<rel_bsout
			sta wrhook
			lda #>rel_bsout
			sta wrhook+1
			pla
			HANDLE_EVENT REL_EVENT_CKOUT

; Clears I/O channels
;
rel_clrch:	HANDLE_EVENT REL_EVENT_CLRCH

; Sets record position
; INPUT:
;	A/Y ... record no.
;	X ..... channel no.
;	ptr2 .. position in record
;
rel_setp:	HANDLE_EVENT REL_EVENT_POS

; Reads byte from file
;
rel_basin:	HANDLE_EVENT REL_EVENT_BASIN

; Writes byte to file
;
rel_bsout:	HANDLE_EVENT REL_EVENT_BSOUT

;----------------------------------------------------------
; Helpers
;

; Calculates file position for a record
; INPUT:
;	X .......... record size
;   A/Y	........ record number
; OUTPUT:
;   (ptr2)/Y ... position of record in file
; NOTE:
;	Uses ptr & ptr2 for calculations
;
calc_pos:	sta ptr
			sty ptr+1
			lda #0
			sta ptr2
			sta ptr2+1
			tay
			txa
@mul_loop:	lsr A
			tax
			bcc @next
			clc
			lda ptr
			adc ptr2
			sta ptr2
			lda ptr+1
			adc ptr2+1
			sta ptr2+1
			bcc @next
			iny
@next:		asl ptr
			rol ptr+1
			txa
			bne @mul_loop
			rts

; Calculates record block & offset
; INPUT:
; 	rec_no ..... record number
; 	rec_size ... record size
;	rec_pos .... position in record
; OUTPUT:
;	rec_block .. record block
;	rec_offset . offset in record block
;
setpos:		lda rec_no
			ldy rec_no+1
			ldx rec_size
			jsr calc_pos
			lda ptr2
			clc
			adc rec_pos
			sta rec_offset
			lda ptr2+1
			adc #0
			sta rec_block
			tya
			adc #0
			sta rec_block+1
			rts

; Checks if a record is new
; RETURN:
;	Carry = 0 ... Record exists
;	Carry = 1 ... Record is new
;
checkrec:  lda rec_dir
			sta ptr
			lda rec_dir+1
			sta ptr+1
			ldy #DirEntry::blocks
			clc
			lda (ptr), y
			sbc rec_block
			sta ptr2
			iny
			lda (ptr), y
			sbc rec_block+1
			bcc @recnew
			ora ptr2
			bne @inrec			
			; check losize
			ldy #DirEntry::losize
			lda (ptr), y
			beq @inrec			
			lda rec_offset
			cmp (ptr), y
			bcs @recnew
@inrec:		clc
			rts
@recnew:	sec
			rts


; Checks if a record is new for writing 
; SET_STATE REL_STATE_RECNEW | REL_STATE_INREC
;
checkrecw:  jsr checkrec
			bcs @recnew
@inrec:		SET_STATE REL_STATE_INREC
			SET_RESULT STATUS_OK
			clc
			rts
@recnew:	SET_STATE REL_STATE_RECNEW			
			SET_RESULT STATUS_RECORD_NOT_PRESENT
@done:		clc
			rts

	.segment "BSS"
	
rec_i:		.res 1
rec_j:		.res 1
rec_lo:		.res 1
rec_hi:		.res 1
rec_p:		.res 1

outb:		.res 1
	
	.segment "CODE"

; Grows file up to and including current record
; INPUT:
;	rec_no .... record number
;	rec_dir ... ptr to directory entry
;
grow:		; init record count
			clc
			lda rec_no
			adc #1
			sta rec_i
			lda rec_no+1
			adc #0
			sta rec_j
			; is file new?
			lda rec_dir
			sta ptr
			lda rec_dir+1
			sta ptr+1
			ldy #DirEntry::bank
			lda (ptr), y
			bne @append
			; create file with enough records
			lda #255
			jsr writefrst
			bcs @fail
@fillrec:	ldx rec_size
@clrloop:	lda #0
			dex
			beq @done_rec
			jsr writenext
			bcc @clrloop
			bcs @fail
@done_rec:	dec rec_i
			bne @next
			ldx rec_j
			beq @done
			dex
			stx rec_j			
@next:		lda #255
			jsr writenext
			bcc @fillrec
			bcs @fail
@done:		; TODO?: save position (bank, address from last record start)
			clc
@fail:		ldx #IO_MODE_REL
			stx outinfo+FileInfo::mode
			jmp io_setout
@append:	; append to file
			ldx rec_info
			jsr seekend
			ldx rec_info
			jsr setinfout			
			lda outinfo+FileInfo::addr
			sta wr_adr+2
			; calc position in record for appending
			ldy #DirEntry::blocks
			lda (ptr), y
			sta rec_lo
			iny 
			lda (ptr), y
			sta rec_hi
			ldy #DirEntry::losize
			lda (ptr), y
			sta rec_p
			beq @nodec
			lda rec_lo
			bne @declo
			dec rec_hi
@declo:		dec rec_lo			
@nodec:		sec
@subloop:	ldx rec_i
			bne @lorec
			dec rec_j
@lorec:		dex
			stx rec_i 
			lda rec_p
			sbc rec_size
			sta rec_p
			lda rec_lo
			sbc #0
			sta rec_lo
			lda rec_hi
			sbc #0
			sta rec_hi
			bcs @subloop
			; revert last subtraction
			lda rec_p
			adc rec_size
			sta rec_p
			lda rec_lo
			adc #0
			sta rec_lo
			lda rec_hi
			adc #0
			sta rec_hi
			inc rec_i
			bne @noj
			inc rec_j			
			; set-up record pointer & append
@noj:		lda rec_p
			bne @notfirst
			jmp @next
@notfirst:	sec
			lda rec_size
			sbc rec_p
			tax
			jmp @clrloop 

; Gets current position
; INPUT:
;	rec_block .. record block
;	rec_dir .... ptr to directory
; OUTPUT:
;	rec_bank ... RAM bank
;	rec_addr ... high byte of block address
; RETURN:
;	Carry ... Success (0) or end of chain (1)
;
calc_ptr:	lda rec_dir
			sta ptr
			lda rec_dir+1
			sta ptr+1
			ldy #DirEntry::address
			lda (ptr), y
			sta rec_addr
			ldy #DirEntry::bank
			lda (ptr), y
			ldy rec_addr
			ldx rec_block+1
			stx rec_j
			ldx rec_block
			stx rec_i
			beq @done
@seeklp:	jsr md_next
			bcs @fail
			dec rec_i
			bne @seeklp
			ldx rec_j
			beq @done
			dec rec_j
			bpl @seeklp
@done:		sta rec_bank
			sty rec_addr
			clc
			rts	
@fail:		sec
			rts

; Sets write position
; INPUT:
;	rec_block .. record block
;	rec_offset . offset in record block
;	rec_dir .... ptr to directory
; OUTPUT:
;	rec_bank ... RAM bank
;	rec_addr ... high byte of block address
;	iowadr ..... full address
; RETURN:
;	Carry ... Success (0) or end of chain (1)
;
setwpos:	jsr calc_ptr
setwadr:	lda rec_offset
			sta iowadr
			lda rec_addr
			sta iowadr+1
			rts

; Sets read position
; INPUT:
;	rec_block .. record block
;	rec_offset . offset in record block
;	rec_dir .... ptr to directory
; OUTPUT:
;	rec_bank ... RAM bank
;	rec_addr ... high byte of block address
;	ioradr ..... full address
; RETURN:
;	Carry ... Success (0) or end of chain (1)
;
setrpos:	jsr calc_ptr
setradr:	lda rec_offset
			sta ioradr
			lda rec_addr
			sta ioradr+1
			rts

; Writes byte to record
; INPUT:
;	A .... byte to write
;
write:		ldx rec_pos				; already at end?
			cpx rec_size
			bcs @eor
			ldx rec_bank			; write byte
			ldy FE3_REG1
			jsr io_write
			inc rec_pos				; update pos info
			inc rec_offset
			beq @nextblock
			inc iowadr
			bne @setok
@eor:		SET_RESULT STATUS_RECORD_OVERFLOW
			rts
@nextblock:	lda rec_bank			; get next block
			ldy rec_addr
			jsr md_next
			bcs @eor
			sta rec_bank
			sty rec_addr
			inc rec_block			; update current block index
			bne @setres
			inc rec_block+1
@setres:	jsr setwadr
@setok:		SET_RESULT STATUS_OK
			ldx rec_pos
			cpx rec_size
			bcs @done
			lda #0
			ldx rec_bank			; write end marker
			ldy FE3_REG1
			jmp io_write
@done:		rts			

; Reads byte from record
; OUTPUT:
;	A .... byte read
;
read:		ldx rec_bank			; read byte
			ldy FE3_REG1
			jsr io_read
			pha
			cmp #$FF				; record deleted?
			beq @eor
			inc rec_pos				; update pos info
			inc rec_offset
			beq @nextblock
			inc ioradr
@peek:		lda rec_pos
			cmp rec_size
			bcs @eor
			jsr io_read
			beq @eor
			SET_RESULT STATUS_OK
			pla
			rts
@eor:		; end of record -> next
			inc rec_no
			bne @clrpos
			inc rec_no+1
@clrpos:	lda #0
			sta rec_pos
			jsr setpos
			jsr checkrec
			bcc @inrec
			SET_STATE REL_STATE_READVOID
@inrec:		SET_RESULT STATUS_OK
			SET_EOF
			pla
			rts
@nextblock:	lda rec_bank			; get next block
			ldy rec_addr
			jsr md_next
			bcs @eof
			sta rec_bank
			sty rec_addr
			inc rec_block			; update current block index
			bne @setres
			inc rec_block+1
@setres:	jsr setradr
			ldx rec_bank
			ldy FE3_REG1
			jmp @peek
@eof:		SET_RESULT STATUS_RECORD_NOT_PRESENT
			SET_STATE REL_STATE_READVOID
			SET_EOF
			pla
			rts

;----------------------------------------------------------
; I/O routines
;

;-----------------;
; REL_STATE_CLOSE ;
;-----------------;

; REL_EVENT_OPEN
cl_open:	; save file no & record size
			sta rec_chn
			stx rec_info
			sty rec_size
			; save dir entry ptr
			lda ptr
			sta rec_dir
			lda ptr+1
			sta rec_dir+1
			; set record position
			lda #0
			sta rec_no
			sta rec_no+1
			sta rec_pos
			jsr setpos
			jsr checkrecw
			SET_RESULT STATUS_OK
			rts

; REL_EVENT_CLOSE
cl_close:	clc			; ignore
			rts
; REL_EVENT_CKOUT
cl_ckout:	sec			; Fatal error, file not open
			rts
; REL_EVENT_CHKIN,
cl_chkin:	sec			; Fatal error, file not open
			rts
; REL_EVENT_CLRCH
cl_clrch:	clc			; nop
			rts
; REL_EVENT_POS
cl_pos:		SET_RESULT STATUS_NO_CHANNEL
			sec
			rts
; REL_EVENT_BASIN
cl_basin:	sec			; Fatal error, file not open
			rts
; REL_EVENT_BSOUT
cl_bsout:	sec			; Fatal error, file not open
			rts
				
;------------------;
; REL_STATE_RECNEW ;
;------------------;

; REL_EVENT_OPEN
rn_open:	SET_RESULT STATUS_OK
			sec			; fail (highlander)
			rts
; REL_EVENT_CLOSE
rn_close:	SET_RESULT STATUS_OK
			SET_STATE REL_STATE_CLOSE
			rts
; REL_EVENT_CKOUT
rn_ckout:	SET_STATE REL_STATE_WRITENEW	
			rts
; REL_EVENT_CHKIN,
rn_chkin:	SET_STATE REL_STATE_READVOID
			rts
; REL_EVENT_CLRCH
rn_clrch:	rts
; REL_EVENT_POS
rn_pos:		cpx rec_chn
			beq @chok
			SET_RESULT STATUS_NO_CHANNEL
			sec
			rts
@chok:		tax
			bne @nz
			dey
@nz:		dex			
			lda ptr2
			cmp rec_size
			bcs @ill_track
			sta rec_pos
			stx rec_no
			sty rec_no+1
			jsr setpos
			jmp checkrecw
@ill_track:	SET_RESULT STATUS_ILLEGAL_TRACK
			sec
			rts
		
; REL_EVENT_BASIN
rn_basin = cl_basin
; REL_EVENT_BSOUT
rn_bsout = cl_bsout

;-----------------;
; REL_STATE_INREC ;
;-----------------;

; REL_EVENT_OPEN
ir_open = rn_open
; REL_EVENT_CLOSE
ir_close = rn_close
; REL_EVENT_CKOUT
ir_ckout:	jsr setwpos
			SET_STATE REL_STATE_WRITEREC
			rts
; REL_EVENT_CHKIN,
ir_chkin:	jsr setrpos
			SET_STATE REL_STATE_READREC
			rts
; REL_EVENT_CLRCH
ir_clrch:	rts
; REL_EVENT_POS
ir_pos = rn_pos
; REL_EVENT_BASIN
ir_basin = cl_basin
; REL_EVENT_BSOUT
ir_bsout = cl_bsout

;--------------------;
; REL_STATE_READVOID ;
;--------------------;

; REL_EVENT_OPEN
rv_open = rn_open
; REL_EVENT_CLOSE
rv_close = rn_close
; REL_EVENT_CKOUT
rv_ckout:	rts
; REL_EVENT_CHKIN,
rv_chkin:	rts
; REL_EVENT_CLRCH
rv_clrch:	SET_STATE REL_STATE_RECNEW
			rts
; REL_EVENT_POS
rv_pos:		rts
; REL_EVENT_BASIN
rv_basin:	SET_EOF
			lda #13
			rts
; REL_EVENT_BSOUT
rv_bsout = cl_bsout

;--------------------;
; REL_STATE_WRITENEW ;
;--------------------;

; REL_EVENT_OPEN
wn_open = rn_open
; REL_EVENT_CLOSE
wn_close = rn_close
; REL_EVENT_CKOUT
wn_ckout:	rts
; REL_EVENT_CHKIN,
wn_chkin:	rts
; REL_EVENT_CLRCH
wn_clrch:	SET_STATE REL_STATE_RECNEW
			rts
; REL_EVENT_POS
wn_pos:		rts
; REL_EVENT_BASIN
wn_basin = cl_basin
; REL_EVENT_BSOUT
wn_bsout:	; create record(s)
			sta outb 
			txa
			pha
			tya
			pha
			jsr grow
			bcs @wvoid
			; write byte
			jsr setwpos
			lda outb
			jsr write
			; next state
@sets:		SET_STATE REL_STATE_WRITEREC
			SET_RESULT STATUS_OK
			pla
			tay
			pla
			tax
			lda #0
			clc
			rts
@wvoid:		SET_STATE REL_STATE_WRITEVOID
			SET_RESULT STATUS_FILE_TOO_LARGE
			pla
			tay
			pla
			tax
			lda #ERR_TOO_MANY_FILES
			sec
			rts

;---------------------;
; REL_STATE_WRITEVOID ;
;---------------------;

; REL_EVENT_OPEN
wv_open = rn_open
; REL_EVENT_CLOSE
wv_close = rn_close
; REL_EVENT_CKOUT
wv_ckout:	rts
; REL_EVENT_CHKIN,
wv_chkin:	rts
; REL_EVENT_CLRCH
wv_clrch:	SET_STATE REL_STATE_RECNEW
			rts
; REL_EVENT_POS
wv_pos:		rts
; REL_EVENT_BASIN
wv_basin = cl_basin
; REL_EVENT_BSOUT
wv_bsout:	rts

;--------------------;
; REL_STATE_WRITEREC ;
;--------------------;

; REL_EVENT_OPEN
wr_open = rn_open
; REL_EVENT_CLOSE
wr_close = rn_close
; REL_EVENT_CKOUT
wr_ckout:	rts
; REL_EVENT_CHKIN,
wr_chkin:	rts
; REL_EVENT_CLRCH
wr_clrch:	; select next record
			inc rec_no
			bne @clrpos
			inc rec_no+1
@clrpos:	lda #0
			sta rec_pos
			jsr setpos
			jmp checkrecw 
; REL_EVENT_POS
wr_pos:		rts
; REL_EVENT_BASIN
wr_basin = cl_basin
; REL_EVENT_BSOUT
wr_bsout:	PUSHXY
			jsr write
			POPXY
			rts

;-------------------;
; REL_STATE_READREC ;
;-------------------;

; REL_EVENT_OPEN
rr_open = rn_open
; REL_EVENT_CLOSE
rr_close = rn_close
; REL_EVENT_CKOUT
rr_ckout:	rts
; REL_EVENT_CHKIN,
rr_chkin:	rts
; REL_EVENT_CLRCH
rr_clrch:	SET_STATE REL_STATE_INREC
			rts
; REL_EVENT_POS
rr_pos:		rts
; REL_EVENT_BASIN
rr_basin:	PUSHXY
			jsr read
			POPXY
			rts
; REL_EVENT_BSOUT
rr_bsout = cl_bsout

	.segment "RODATA"

; state event table
;	REL_STATE_CLOSE, REL_STATE_RECNEW, REL_STATE_INREC, REL_STATE_READVOID,
;	REL_STATE_WRITENEW, REL_STATE_WRITEVOID, REL_STATE_WRITEREC,
;	REL_STATE_READREC
	
sttbl_lo:	.byte <etbl0, <etbl1, <etbl2, <etbl3, <etbl4, <etbl5, <etbl6, <etbl7
sttbl_hi:	.byte >etbl0, >etbl1, >etbl2, >etbl3, >etbl4, >etbl5, >etbl6, >etbl7

	.segment "TABLE"

;	REL_STATE_CLOSE:
;		REL_EVENT_OPEN, REL_EVENT_CLOSE, REL_EVENT_CKOUT, REL_EVENT_CHKIN,
;		REL_EVENT_CLRCH, REL_EVENT_POS, REL_EVENT_BASIN, REL_EVENT_BSOUT
etbl0:		.word cl_open, cl_close, cl_ckout, cl_chkin
			.word cl_clrch, cl_pos, cl_basin, cl_bsout
				
;	REL_STATE_RECNEW:
;		REL_EVENT_OPEN, REL_EVENT_CLOSE, REL_EVENT_CKOUT, REL_EVENT_CHKIN,
;		REL_EVENT_CLRCH, REL_EVENT_POS, REL_EVENT_BASIN, REL_EVENT_BSOUT
etbl1:		.word rn_open, rn_close, rn_ckout, rn_chkin
			.word rn_clrch, rn_pos, rn_basin, rn_bsout

;	REL_STATE_INREC:
;		REL_EVENT_OPEN, REL_EVENT_CLOSE, REL_EVENT_CKOUT, REL_EVENT_CHKIN,
;		REL_EVENT_CLRCH, REL_EVENT_POS, REL_EVENT_BASIN, REL_EVENT_BSOUT
etbl2:		.word ir_open, ir_close, ir_ckout, ir_chkin
			.word ir_clrch, ir_pos, ir_basin, ir_bsout

;	REL_STATE_READVOID:
;		REL_EVENT_OPEN, REL_EVENT_CLOSE, REL_EVENT_CKOUT, REL_EVENT_CHKIN,
;		REL_EVENT_CLRCH, REL_EVENT_POS, REL_EVENT_BASIN, REL_EVENT_BSOUT
etbl3:		.word rv_open, rv_close, rv_ckout, rv_chkin
			.word rv_clrch, rv_pos, rv_basin, rv_bsout

;	REL_STATE_WRITENEW:
;		REL_EVENT_OPEN, REL_EVENT_CLOSE, REL_EVENT_CKOUT, REL_EVENT_CHKIN,
;		REL_EVENT_CLRCH, REL_EVENT_POS, REL_EVENT_BASIN, REL_EVENT_BSOUT
etbl4:		.word wn_open, wn_close, wn_ckout, wn_chkin
			.word wn_clrch, wn_pos, wn_basin, wn_bsout

;	REL_STATE_WRITEVOID:
;		REL_EVENT_OPEN, REL_EVENT_CLOSE, REL_EVENT_CKOUT, REL_EVENT_CHKIN,
;		REL_EVENT_CLRCH, REL_EVENT_POS, REL_EVENT_BASIN, REL_EVENT_BSOUT
etbl5:		.word wv_open, wv_close, wv_ckout, wv_chkin
			.word wv_clrch, wv_pos, wv_basin, wv_bsout

;	REL_STATE_WRITEREC:
;		REL_EVENT_OPEN, REL_EVENT_CLOSE, REL_EVENT_CKOUT, REL_EVENT_CHKIN,
;		REL_EVENT_CLRCH, REL_EVENT_POS, REL_EVENT_BASIN, REL_EVENT_BSOUT
etbl6:		.word wr_open, wr_close, wr_ckout, wr_chkin
			.word wr_clrch, wr_pos, wr_basin, wr_bsout

;	REL_STATE_READREC:
;		REL_EVENT_OPEN, REL_EVENT_CLOSE, REL_EVENT_CKOUT, REL_EVENT_CHKIN,
;		REL_EVENT_CLRCH, REL_EVENT_POS, REL_EVENT_BASIN, REL_EVENT_BSOUT
etbl7:		.word rr_open, rr_close, rr_ckout, rr_chkin
			.word rr_clrch, rr_pos, rr_basin, rr_bsout
